/*
 * Copyright (c) 2012, Markus Weber All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer. Redistributions in binary form must reproduce the
 * above copyright notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the author nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */
package de.dfki.graph.subgraphmatching;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.math.stat.StatUtils;
import org.apache.commons.math.util.MathUtils;
import org.apache.log4j.Logger;

import de.dfki.graph.EmptyQueryException;
import de.dfki.graph.LabeledEdge;
import de.dfki.graph.LabeledGraph;
import de.dfki.graph.LabeledVertex;
import de.dfki.graph.index.DecisionTree;
import de.dfki.graph.index.SearchResult;
import de.dfki.graph.utils.BigMathUtils;

/**
 * Extended subgraph matching method developed by Messmer and Bunke.
 *
 * @author Markus Weber
 *
 */
public class MessmerBunkeSubgraphMatcher implements IndexedSubGraphMatcher {
    /**
     * Logger
     */
    private Logger log = Logger.getLogger(MessmerBunkeSubgraphMatcher.class);
    /**
     * Decision tree for retrieval.
     */
    private DecisionTree tree = new DecisionTree();
    /**
     * Flag if index is build.
     */
    private boolean indexBuild = false;
    /**
     * Reference to current graph.
     */
    private LabeledGraph current = null;
    /**
     * Weight Mapping function sigma
     */
    private Map<String, Byte> sigma;
    /**
     * Weight sequence.
     */
    private byte weightSequence = 0;
    /**
     * Synchronization lock
     */
    private final Object lock = new Object();

    /**
     * Helper Class to sort the indexing jobs
     *
     * @author m_weber
     *
     */
    class IndexJob implements Comparable<IndexJob> {
        public LabeledGraph graph;
        public BigDecimal permutations;

        public IndexJob(LabeledGraph graph, BigDecimal estimatePermutation) {
            this.graph = graph;
            this.permutations = estimatePermutation;
        }

        @Override
        public int compareTo(IndexJob o) {
            return permutations.compareTo(o.permutations);
        }
    }

    /**
     * Constructor with assigned sigma mapping
     *
     * @param sigma
     */
    public MessmerBunkeSubgraphMatcher(Map<String, Byte> sigma) {
        super();
        this.sigma = sigma;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.dfki.structures.graph.IndexedSubGraphMatcher#buildIndex(java
     * .util.List)
     */
    @Override
    public void buildIndex(Collection<LabeledGraph> graphs) {
        this.buildIndex(graphs, null);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.dfki.structures.graph.IndexedSubGraphMatcher#buildIndex(java.util.
     * Collection, java.io.Writer)
     */
    @Override
    public void buildIndex(Collection<LabeledGraph> todos, Writer debug) {
        if (debug != null) {
            try {
                debug.write("graph name;number of vertices;number of different labels;max same label;computation time;permutations;number of nodes;memory\n");
            } catch (IOException e1) {
                log.error(e1);
            }
        }
        SortedSet<IndexJob> jobs = new TreeSet<MessmerBunkeSubgraphMatcher.IndexJob>();
        for (LabeledGraph graph : todos) {
            // assign the weights
            sigma(graph);
            if (tree.containsDecision(graph.getName())) {
                log.warn("Graph " + graph.getName()
                        + " is already added to decision tree.");
                continue;
            }
            jobs.add(new IndexJob(graph, estimatePermutation(graph)));
        }
        for (IndexJob job : jobs) {
            current = job.graph;
            int count = 0;
            log.info("Adding graph " + current.getName()
                    + " to decision tree. #vertices="
                    + current.numberOfVertices() + " #same labels : "
                    + current.maxSameLabels() + " #estimated permutations : "
                    + estimatePermutation(current));
            LabeledVertex<?>[] entities = new LabeledVertex<?>[current.numberOfVertices()];
            long start = System.currentTimeMillis();
            // Sorting the entities according the order
            for (LabeledVertex<?> e : current) { // order
                entities[count] = e;
                count++;
            }
            // Iterate over all entities
            List<Integer> omissions = new ArrayList<Integer>();
            List<List<LabeledVertex<?>[]>> memory = new ArrayList<List<LabeledVertex<?>[]>>();
            for (int i = 0; i < entities.length;) {
                List<LabeledVertex<?>[]> omission = new LinkedList<LabeledVertex<?>[]>();
                // consider the omission
                omission.add(new LabeledVertex<?>[0]);
                if (i == entities.length - 1) {
                    determineOmission(entities, i, entities.length,
                            current.getName(), omissions, omission);
                    i = entities.length;
                } else {
                    int begin = i;
                    int end = i + 1;
                    while (entities[begin].getType().getWeight() == entities[end].getType().getWeight()) {
                        end++;
                        if (end == entities.length) {
                            break;
                        }
                    }
                    determineOmission(entities, begin, end, current.getName(),
                            omissions, omission);
                    i = end;
                }
                memory.add(omission);
            }
            long stop = System.currentTimeMillis();
            log.info(current.getName() + ": time to determine variations: "
                    + (stop - start) + " ms");
            start = System.currentTimeMillis();

            long permutations = cartProduct(memory, current.getName());
            stop = System.currentTimeMillis();
            if (debug != null) {
                writeResults(current, debug, start, permutations, stop);
            }
            log.info(current.getName()
                    + ": time to determine cartesian product: "
                    + (stop - start) + " ms");
            log.info(current.getName() + ": determined permutations: "
                    + permutations);
            permutations = 0;
        }
        indexBuild = true;

    }

    /**
     * Estimates the number of permutations
     *
     * @return
     */
    public BigDecimal estimatePermutation(LabeledGraph g) {
        BigDecimal all = new BigDecimal("1");
        synchronized (lock) {
            for (String s : g.getLabelMap().keySet()) {
                all = all.multiply(labelOmissions(g.getLabelMap().get(s).size()));
            }
        }
        return all;
    }

    /**
     * Determines the variations.
     *
     * @param n
     * @return
     */
    private BigDecimal labelOmissions(int n) {
        BigDecimal variation = new BigDecimal("1");
        for (int i = 1; i <= n; i++) {
            try {
                // 170! should be big enough
                variation = variation.add(new BigDecimal(MathUtils.factorialDouble(n)
                        / MathUtils.factorialDouble(n - i)));
            } catch (ArithmeticException e) {
                System.out.println(e);
            }
        }
        return variation;
    }

    /**
     * Used memory
     *
     * @return
     */
    private long usedMemory() {
        Runtime rt = Runtime.getRuntime();
        return rt.totalMemory() - rt.freeMemory();
    }

    /**
     * Assigns the weights to the labels.
     *
     * @param g
     */
    public void sigma(LabeledGraph g) {
        synchronized (lock) {
            for (LabeledVertex<?> v : g.vertexSet()) {
                String label = v.getType().getValue().toString();
                byte w = 0;
                if (this.sigma.containsKey(label)) {
                    w = this.sigma.get(label);
                } else {
                    weightSequence++;
                    this.sigma.put(label.toString(), weightSequence);
                    w = weightSequence;
                    log.debug("New label type [value]=" + label + " [weight]="
                            + weightSequence);
                }
                v.getType().updateWeight(w);
            }
        }
    }

    /**
     * Generate the sequence.
     *
     * @param current
     * @param len
     * @return
     */
    private byte[] generateSequence(LabeledVertex<?>[] current, int len) {
        byte[] sequence = new byte[2 * len + 1];
        sequence[len] = current[len].getType().getWeight();
        for (int j = 0; j < len; j++) {
            LabeledEdge<?> e = this.current.getEdge(current[len], current[len
                    - (j + 1)]);
            sequence[j] = e != null ? e.getLabel().getWeight() : 0;
            e = this.current.getEdge(current[len - (j + 1)], current[len]);
            sequence[len + 1 + (len - (j + 1))] = e != null ? e.getLabel().getWeight() : 0;
        }
        return sequence;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.dfki.structures.graph.IndexedSubGraphMatcher#isIndexBuild()
     */
    @Override
    public boolean isIndexBuild() {
        return indexBuild;
    }

    /*
     * (non-Javadoc)
     *
     * @see de.dfki.structures.graph.IndexedSubGraphMatcher#clearIndex()
     */
    @Override
    public void clearIndex() {
        this.tree.clear();
    }

    /*
     * (non-Javadoc)
     *
     * @see de.dfki.structures.graph.IndexedSubGraphMatcher#storeIndex(java
     * .io.File)
     */
    @Override
    public void storeIndex(File index) {
        try {
            ObjectOutputStream stream = new ObjectOutputStream(
                    new FileOutputStream(index));
            stream.writeObject(this.tree);
            stream.close();
        } catch (Exception e) {
            log.error("Storing index failed. [EXCEPTION]"
                    + e.getLocalizedMessage());
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see de.dfki.structures.graph.IndexedSubGraphMatcher#loadIndex(java
     * .io.File)
     */
    @Override
    public void loadIndex(File index) {
        try {
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                    index));
            this.tree = (DecisionTree) oin.readObject();
        } catch (Exception e) {
            log.error("Loading index failed. [EXCEPTION]"
                    + e.getLocalizedMessage());
        }
    }

    /**
     * Generates the combinations n over k.
     *
     * @param elements
     * @return
     */
    private List<ArrayList<LabeledVertex<?>>> generateCombinations(
            LabeledVertex<?>[] elements) {
        List<ArrayList<LabeledVertex<?>>> res = new ArrayList<ArrayList<LabeledVertex<?>>>();
        int combinations = (int) Math.pow(2, elements.length);
        for (int i = 1; i < combinations; i++) {
            String binary = fillWithZeros(Integer.toBinaryString(i),
                    elements.length);
            res.add((ArrayList<LabeledVertex<?>>) getCombination(binary,
                    elements));
        }
        return res;
    }

    /**
     * Calculates the cartesian product.
     *
     * @param sets
     * @param fp
     * @return
     */
    private long cartProduct(List<List<LabeledVertex<?>[]>> sets, String fp) {
        List<LabeledVertex<?>[]> cartesian_product_element;
        long n = 1;
        long num = 0;
        long start = System.currentTimeMillis();
        Iterator<List<LabeledVertex<?>[]>> sets_it = sets.iterator();
        // loop to get cardinality of Cartesian product
        while (sets_it.hasNext()) {
            List<LabeledVertex<?>[]> set = sets_it.next();
            n *= set.size();
        }
        long report = n / 100;
        // loop to create all elements of Cartesian product
        for (long i = 0; i < n; i++) {
            long j = 1;
            cartesian_product_element = new ArrayList<LabeledVertex<?>[]>();
            sets_it = sets.iterator();
            // loop that collects one element of each class
            while (sets_it.hasNext()) {
                List<LabeledVertex<?>[]> set = sets_it.next();
                cartesian_product_element.add(set.get(((int) (i / j))
                        % set.size()));
                j *= set.size();
            }
            long todo = n - num;
            if (report > 0 && todo % report == 0) {
                double mem = usedMemory() / (1024.0 * 1024.0);
                mem = Math.round(mem * 100) / 100.0;
                System.out.printf(
                        " %.2f percent done after %.2f s. (%.2f MB)\n",
                        (100 - ((todo / (n * 1.)) * 100)),
                        ((System.currentTimeMillis() - start) / 1000.), mem);
            }
            addSequences(cartesian_product_element, fp);
            num++;
        }
        return num;
    }

    /**
     * Determines the omission.
     *
     * @param entities
     * @param begin
     * @param end
     * @param graph
     * @param om
     * @param memory
     */
    private void determineOmission(LabeledVertex<?>[] entities, int begin,
            int end, String graph, List<Integer> om,
            List<LabeledVertex<?>[]> memory) {
        List<LabeledVertex<?>[]> omissions = new LinkedList<LabeledVertex<?>[]>();
        // ===================== Determine all variations
        // Putting the vertices with the same label in the urn.
        LabeledVertex<?>[] urn = Arrays.copyOfRange(entities, begin, end);
        List<ArrayList<LabeledVertex<?>>> combinations = generateCombinations(urn);
        // Empty combination
        for (List<LabeledVertex<?>> comb : combinations) {
            LabeledVertex<?>[] ent = new LabeledVertex<?>[comb.size()];
            ent = comb.toArray(ent);
            permute(0, comb.size(), ent, omissions);
        }
        // long i = 0;
        om.add(omissions.size());
        for (LabeledVertex<?>[] o : omissions) {
            if (memory == null) {
                // serialize(o, store + "/" + format(i++, omissions.size()));
            } else {
                memory.add(o);
            }
        }
    }

    /**
     * Creating all permutations according to the algorithm of Rosen.
     *
     * @param a
     * @param b
     * @param entities
     * @param fp
     * @param permutations
     */
    private void permute(int a, int b, LabeledVertex<?>[] v,
            List<LabeledVertex<?>[]> permutations) {
        permutations.add(v.clone());
        for (int i = b - 2; i >= a; i--) {
            for (int j = i + 1; j < b; j++) {
                swap(i, j, v);
                permute(i + 1, b, v, permutations);
            }
            rotate(i, b, v);
        }
    }

    /**
     * Swapping two elements.
     *
     * @param a
     * @param b
     * @param v
     * @param fp
     */
    private void swap(int a, int b, LabeledVertex<?>[] v) {
        LabeledVertex<?> t = v[a];
        v[a] = v[b];
        v[b] = t;
    }

    /**
     * Rotating elements in an interval between a and b.
     *
     * @param a
     * @param b
     * @param v
     * @param fp
     */
    private void rotate(int a, int b, LabeledVertex<?>[] v) {
        LabeledVertex<?> t = v[a];
        for (int i = a; i < b - 1; i++) {
            v[i] = v[i + 1];
        }
        v[b - 1] = t;
    }

    /**
     * Creates the sequence for the decision tree.
     *
     * @param v
     * @param graph
     * @return
     */
    private void addSequences(List<LabeledVertex<?>[]> list, String graph) {
        List<LabeledVertex<?>> v = new ArrayList<LabeledVertex<?>>();
        for (LabeledVertex<?>[] e : list) {
            for (LabeledVertex<?> ei : e) {
                v.add(ei);
            }
        }

        if (v.size() == 0) {
            // ignore the empty set
            return;
        }
        LabeledVertex<?>[] ent = new LabeledVertex<?>[v.size()];
        ent = v.toArray(ent);
        addSequences(ent, graph);
    }

    /**
     * Creates the sequence for the decision tree.
     *
     * @param current
     * @param graph
     * @return
     */
    private void addSequences(LabeledVertex<?>[] current, String graph) {
        if (current[0] == null) {
            return;
        }
        for (int len = 0; len < current.length; len++) {
            byte[] sequence = generateSequence(current, len);
            if (sequence.length == 1) {
                tree.addEntryPoint(current[0], graph);
            } else {
                tree.addSequence(len, current[len - 1], current[len], sequence,
                        graph);
            }
        }
    }

    /**
     * Returns the combination for a binary strings.
     *
     * @param binary
     * @param elements
     * @return
     */
    private List<LabeledVertex<?>> getCombination(String binary,
            LabeledVertex<?>[] elements) {
        List<LabeledVertex<?>> res = new ArrayList<LabeledVertex<?>>();
        for (int i = 0; i < binary.length(); i++) {
            if (Integer.parseInt(Character.toString(binary.charAt(i))) == 1) {
                res.add(elements[i]);
            }
        }
        return res;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.dfki.ascatch.structures.graph.IndexedSubGraphMatcher#retrieve(de.dfki
     * .ascatch.structures.graph.QueryGraph, int)
     */
    @Override
    public SortedSet<SearchResult> retrieve(LabeledGraph graph, int M)
            throws EmptyQueryException {
        LabeledVertex<?>[] entities = new LabeledVertex<?>[graph.numberOfVertices()];
        int i = 0;
        List<byte[]> search = new LinkedList<byte[]>();
        for (LabeledVertex<?> e : graph) { // Level-order
            entities[i] = e;
            search.add(generateSequence(entities, i));
            ++i;
        }
        SortedSet<SearchResult> result = tree.lookUp(search);
        return result;
    }

    /**
     * Filling the string with zeros.
     *
     * @param binaryString
     * @param length
     * @return
     */
    private String fillWithZeros(String binaryString, int length) {
        String ret = "";
        int remain = length - binaryString.length();
        if (remain > 0) {
            for (int i = 0; i < remain; i++) {
                ret += "0";
            }
        }
        ret += binaryString;
        return ret;
    }

    @Override
    public void simulateIndex(Collection<LabeledGraph> todos, Writer debug) {
        if (debug != null) {
            try {
                debug.write("graph name;number of vertices;number of different labels;max same label;permutations (modified);permutations (original)\n");
            } catch (IOException e1) {
                log.error(e1);
            }
        }
        Statistics t = new Statistics();
        for (LabeledGraph graph : todos) {
            // assign the weights
            sigma(graph);

            if (debug != null) {
                t.differentypes.add((double) graph.numberOfLabels());
                t.vertices.add((double) graph.numberOfVertices());
                t.sameLabels.add((double) graph.maxSameLabels());
                t.permutationMod.add(estimatePermutation(graph));
                try {
                    t.permutationOrg.add(new BigDecimal(MathUtils.factorialDouble(graph.numberOfVertices())));
                } catch (RuntimeException e) {
                    System.err.println("graph vertices "
                            + graph.numberOfVertices() + " [Exception]" + e);
                }
                writeSimulationResults(graph, debug);
            }

        }
        writeSimulationSummary(t, debug);
    }
    static int line = 1;

    private void writeSimulationSummary(Statistics t, Writer debug) {
        try {
            debug.write("Average;" + StatUtils.mean(toArray(t.vertices)) + ";"
                    + StatUtils.mean(toArray(t.differentypes)) + ";"
                    + StatUtils.mean(toArray(t.sameLabels)) + ";"
                    + t.meanPermutationMod() + ";" + t.meanPermutationOrg()
                    + "\n");
            debug.write("Std deviation;"
                    + Math.sqrt(StatUtils.variance(toArray(t.vertices))) + ";"
                    + Math.sqrt(StatUtils.variance(toArray(t.differentypes)))
                    + ";"
                    + Math.sqrt(StatUtils.variance(toArray(t.sameLabels)))
                    + ";" + t.stddevPermutationMod() + ";"
                    + t.stddevPermutationOrg() + "\n");
            debug.write("Min;" + StatUtils.min(toArray(t.vertices)) + ";"
                    + StatUtils.min(toArray(t.differentypes)) + ";"
                    + StatUtils.min(toArray(t.sameLabels)) + ";" + ";;\n");
            debug.write("Max;" + StatUtils.max(toArray(t.vertices)) + ";"
                    + StatUtils.max(toArray(t.differentypes)) + ";"
                    + StatUtils.max(toArray(t.sameLabels)) + ";" + ";;\n");
            debug.write("Average;" + StatUtils.mean(toArray(t.vertices)) + ";"
                    + StatUtils.mean(toArray(t.differentypes)) + ";"
                    + StatUtils.mean(toArray(t.sameLabels)) + ";"
                    + t.meanPermutationMod() + ";" + t.meanPermutationOrg()
                    + "\n");

            debug.write("Min;" + StatUtils.min(toArray(t.vertices)) + ";"
                    + StatUtils.min(toArray(t.differentypes)) + ";"
                    + StatUtils.min(toArray(t.sameLabels)) + ";" + ";;\n");
            debug.write("Max;" + StatUtils.max(toArray(t.vertices)) + ";"
                    + StatUtils.max(toArray(t.differentypes)) + ";"
                    + StatUtils.max(toArray(t.sameLabels)) + ";" + ";;\n");
            String myalg = t.meanPermutationMod().toString();
            String org = t.meanPermutationOrg().toString();
            String my_num = myalg.substring(0, myalg.indexOf("E"));
            String org_num = org.substring(0, myalg.indexOf("E"));
            String my_exp = myalg.substring(1 + myalg.indexOf("+"));
            String org_exp = org.substring(1 + myalg.indexOf("+"));
            System.out.println(line + "&$\\diameter$&" + StatUtils.mean(toArray(t.vertices))
                    + "&$" + my_num + "\\times 10^{" + my_exp + "}" + "$"
                    + "&$" + org_num + "\\times 10^{" + org_exp + "}" + "$"
                    + "&" + StatUtils.mean(toArray(t.differentypes))
                    + "&" + StatUtils.mean(toArray(t.sameLabels)) + "\\\\");
            line++;
        } catch (IOException e) {
            log.error(e);
        }

    }

    private double[] toArray(List<Double> values) {
        double[] d_arr = new double[values.size()];
        for (int i = 0; i < values.size(); i++) {
            d_arr[i] = values.get(i);
        }
        return d_arr;
    }

    /**
     * @param g
     * @param debug
     * @param start
     * @param permutations
     * @param stop
     */
    private synchronized void writeResults(LabeledGraph g, Writer debug,
            long start, long permutations, long stop) {
        try {
            double mem = usedMemory() / (1024.0 * 1024.0);
            mem = Math.round(mem * 100) / 100.0;
            debug.write(g.getName() + ";" + g.numberOfVertices() + ";"
                    + g.numberOfLabels() + ";" + g.maxSameLabels() + ";"
                    + (stop - start) + ";" + permutations + ";"
                    + tree.numberOfNodes() + ";" + mem + " MB");

            debug.write("\n");
            debug.flush();
        } catch (IOException e) {
            log.error(e);
        }
    }

    class Statistics {

        List<BigDecimal> permutationOrg = new LinkedList<BigDecimal>();
        List<BigDecimal> permutationMod = new LinkedList<BigDecimal>();
        List<Double> vertices = new LinkedList<Double>();
        List<Double> sameLabels = new LinkedList<Double>();
        List<Double> differentypes = new LinkedList<Double>();

        public BigDecimal meanPermutationOrg() {
            return BigMathUtils.mean(this.permutationOrg, new MathContext(2));
        }

        public BigDecimal stddevPermutationOrg() {
            return BigMathUtils.stddev(this.permutationOrg, true,
                    new MathContext(2));
        }

        public BigDecimal stddevPermutationMod() {
            return BigMathUtils.stddev(this.permutationMod, true,
                    new MathContext(2));
        }

        public BigDecimal meanPermutationMod() {
            return BigMathUtils.mean(this.permutationMod, new MathContext(2));
        }
    }

    /**
     * Writes the simulation results.
     *
     * @param graph
     * @param debug
     */
    private void writeSimulationResults(LabeledGraph g, Writer debug) {
        // graph name;number of vertices;number of different labels;max same
        // label;permutations (modified);permutations (original)
        try {
            debug.write(g.getName()
                    + ";"
                    + g.numberOfVertices()
                    + ";"
                    + g.numberOfLabels()
                    + ";"
                    + g.maxSameLabels()
                    + ";"
                    + estimatePermutation(g).toBigInteger()
                    + ";"
                    + new BigDecimal(MathUtils.factorialDouble(g.numberOfVertices())).toBigInteger());

            debug.write("\n");
        } catch (IOException e) {
            log.error(e);
        }
    }
}
